package com.panfeng.xcloud.common.security.interceptor;

import com.alibaba.fastjson.JSON;
import com.panfeng.xcloud.common.core.constants.Constants;
import com.panfeng.xcloud.common.core.constants.GlobalConstant;
import com.panfeng.xcloud.common.core.constants.RedisConstants;
import com.panfeng.xcloud.common.core.enums.ResponseStatusEnum;
import com.panfeng.xcloud.common.core.thread.ThreadLocalMap;
import com.panfeng.xcloud.common.core.utils.UuidUtils;
import com.panfeng.xcloud.common.core.web.vo.ResponseUtils;
import com.panfeng.xcloud.common.core.web.vo.ResponseVO;
import com.panfeng.xcloud.common.security.annotation.NotNeedAccess;
import com.panfeng.xcloud.common.security.vo.SysSecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 *
 * oauth2 access_token拦截
 *
 * @author xiaobo
 * @version 1.0
 * @since 2018-12-28
 */
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	private static final String OPTIONS = "OPTIONS";
	private static final String LOGIN_PATH = "/sys/login";
	private static final String OAUTH_PATH = "/oauth";
	private static final String ERROR_PATH = "/error";
	private static final String API_PATH = "/api";
	private static final String USER_LOGIN_PATH = "/user/login/loginToken";
	private static final String USER_OAUTH_PATH = "/auth/openid";

	private static final ThreadLocal<Long> apiDuration = new ThreadLocal<>();

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object arg2, Exception ex) throws Exception {
		Long startTime = apiDuration.get();
		startTime = null != startTime ? startTime : 0L;
		Long endTime =  System.currentTimeMillis();
		String url = request.getRequestURI();
		log.info(">>> 请求URL:{} =>开始时间：{} =>结束时间：{} =>耗时：{}毫秒 <<<",url ,startTime ,endTime ,(endTime -  startTime));
		if (ex != null) {
			log.error(">>> afterCompletion 解析token失败. ex={} <<<", ex.getMessage(), ex);
			this.handleExceptionResponse(response);
		}
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView mv) {
	}

	/**
	 * 设置上下文
	 *
	 * @param request
	 */
	private void putLogContext(HttpServletRequest request){
		String xd_request_id = request.getHeader("xd-request-id");
		xd_request_id = com.panfeng.xcloud.common.core.utils.StringUtils.isEmpty(xd_request_id) ? "": xd_request_id;

		String xd_Agent = request.getHeader("xd-agent");
		xd_Agent = com.panfeng.xcloud.common.core.utils.StringUtils.isEmpty(xd_Agent) ? "": xd_Agent;

		String Xd_VersionName = request.getHeader("xd-version-name");
		Xd_VersionName = com.panfeng.xcloud.common.core.utils.StringUtils.isEmpty(Xd_VersionName) ? "": Xd_VersionName;

		String Xd_VersionCode = request.getHeader("xd-version-code");
		Xd_VersionCode = com.panfeng.xcloud.common.core.utils.StringUtils.isEmpty(Xd_VersionCode) ? "": Xd_VersionCode;

		String generateUUID = UuidUtils.generateUUID() + System.currentTimeMillis();
		MDC.put(Constants.MDC_KEY, "xd-version-code:" + Xd_VersionCode +  ",xd-version-name:" + Xd_VersionName + ",xd-agent:" + xd_Agent + ",xd_request_id:" + xd_request_id + ",uuid_" + generateUUID);
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
		Long startTime = System.currentTimeMillis();
		apiDuration.set(startTime);
		//设置上下文
		putLogContext(request);
		String uri = request.getRequestURI();

		log.info(">>> preHandle 权限拦截器.  url={} <<<", uri);
		if (uri.contains(LOGIN_PATH) || uri.contains(OAUTH_PATH) || uri.contains(ERROR_PATH) || uri.contains(API_PATH) || uri.contains(USER_LOGIN_PATH) || uri.contains(USER_OAUTH_PATH)) {
			log.info("<== preHandle 配置URL不走认证.  url={} <<<", uri);
			return true;
		}

		if (OPTIONS.equalsIgnoreCase(request.getMethod())) {
			log.info(">>> preHandle  OPTIONS不走认证.  url={} <<<", uri);
			return true;
		}

		if (isHasAccess(handler)) {
			log.info(">>> preHandle 不需要认证注解不走认证.  token={} <<<");
			return true;
		}

		String token = StringUtils.substringAfter(request.getHeader(HttpHeaders.AUTHORIZATION), "bearer ");
		if(StringUtils.isEmpty(token)){
			log.info(">>> 从url的queryString提取token数据 <<<");
			token = request.getParameter("token");
		}

		log.info(">>> preHandle 权限拦截器.  token={} <<<", token);
		String accessTokenKey = RedisConstants.XDCLOUD_COMMON_ACCESS_TOKEN_PREFIX + token;
		String userInfoDtoString = stringRedisTemplate.opsForValue().get(accessTokenKey);
		log.info(">>> preHandle 权限拦截器.  userInfoDtoString={} <<<", userInfoDtoString);
		SysSecurityUser sysSecurityUser = JSON.parseObject(StringUtils.isEmpty(userInfoDtoString) ? "" : userInfoDtoString, SysSecurityUser.class);

		if (StringUtils.isEmpty(userInfoDtoString) || sysSecurityUser == null) {
			log.error(">>> 用户登录信息失效，已退出，请重新登录 <<<");
			throw new AccessDeniedException("用户登录信息失效");
		}

		//将当前用户信息存储在当前线程上下文中
		ThreadLocalMap.put(GlobalConstant.Sys.TOKEN_AUTH_DTO, sysSecurityUser);
		log.info(">>> preHandle 权限拦截器.  url={}, key={}, SecurityUser={} <<<", uri ,GlobalConstant.Sys.TOKEN_AUTH_DTO, sysSecurityUser);

		return true;
	}

	/**
	 * 异常响应
	 *
	 * @param res
	 * @throws IOException
	 */
	private void handleExceptionResponse(HttpServletResponse res) throws IOException {
		res.resetBuffer();
		res.setHeader("Access-Control-Allow-Origin", "*");
		res.setHeader("Access-Control-Allow-Credentials", "true");
		res.setContentType("application/json");
		res.setCharacterEncoding("UTF-8");
		ResponseVO<Object> responseByStatus = ResponseUtils.getResponseByStatus(ResponseStatusEnum.ANALYSIS_TOKEN_FAIL);
		res.getWriter().write(JSON.toJSONString(responseByStatus));
		res.flushBuffer();
	}

	private boolean isHasAccess(Object handler) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		Method method = handlerMethod.getMethod();
		NotNeedAccess notNeedAccess = AnnotationUtils.findAnnotation(method, NotNeedAccess.class);
		return notNeedAccess != null;
	}

}
